package com.briup.day20;

import java.util.Random;

public class ConcurrentTest {
    public static void main(String[] args) {
        /*
            当多个线程同时操作同一份资源时，如4个窗口共同售卖100票，
            可能会出现同时售卖同一张票，或售卖不可兑现的票的情况，对于这种情况，
            我们统称为线程安全问题；
            注意线程安全问题只会出现在多个线程同时操作同一个资源时；
            如果我们让某一个线程在操作资源时，使用某种手段让其他线程不能访问资源，
            等该线程对资源访问完毕后，将该手段释放，让其他线程在访问该资源；如此
            就能有效的解决线程安全问题；即让多个线程不要在同一时间点同时访问一个资源

            即要解决线程安全问题，必须让线程同步；

            线程同步的方式：
                1、使用synchronized关键字
                    synchronized关键字能够修饰代码块和方法；
                    修饰代码块时，其同步锁需要我们自己进行指定，而修饰方法时，
                    同步锁对象不需要我们指定，存在默认的锁对象；如果修饰的是静态方法，
                    那么锁对象默认为 静态方法所在类的类字节码对象（一个类在程序只会存在一个）；
                    如果修饰的是非静态方法（成员方法），那么锁对象是调用该方法的对象
                2、使用java.util.concurrent包下的Lock相关类
         */
        SaleTicket ticket = new SaleTicket("D0001", 100);
        for (int i = 1; i <= 3; i++) {
            Thread t = new Thread(ticket, String.format("窗口%s", i));
            t.start();
        }
        SaleTicket ticket2 = new SaleTicket("G59351", 70);
        for (int i = 1; i <= 2; i++) {
            Thread t = new Thread(ticket2, String.format("窗口%s0", i));
            t.start();
        }
    }
}

// 售票站
class SaleTicket implements Runnable {
    // 售票的车次编号
    private final String no;
    // 当前可售卖的余票
    private int tickets;
    // 当前被售卖的票数
    private int count;
    private static final Random RANDOM = new Random();

    public SaleTicket(String no, int tickets) {
        this.no = no;
        this.tickets = tickets;
    }

    // 封装售票的逻辑
    // synchronized在此处是修饰符，表示本方法是一个同步方法，同时锁资源是this
    private synchronized void sale() throws InterruptedException {
        if (tickets > 0) {
            String name = Thread.currentThread().getName();
            System.out.printf("当前%s正在售卖%s车次的第%s票，还剩%s余票%n",
                    name, no, ++count, --tickets);
            Thread.sleep(RANDOM.nextInt(100, 201));
        }
    }

    @Override
    public void run() {
        while (tickets > 0) {
            // 字面常量使用 ""，那么在整个程序中都使用
            // 同一个位于JVM方法区中字符串常量池内维护的对象；
            // 在此处如果使用 "" 作为锁对象，会将调用所有调用本run()方法的线程都锁住，
            // 无论售卖的是哪一个车次的票，因为整个程序中都是使用同一个""对象
//            synchronized ("") {
            try {
                sale();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
//            }
        }
    }
}